package robocup.component.actionfactories;

import java.util.Collections;
import java.util.LinkedList;

import robocup.component.DAInamiteConstants;
import robocup.component.Prophet;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.actionfactories.SayActionFactory.WeightedPlayer;
import robocup.component.actions.AttentionToAction;
import robocup.component.speechacts.MessageFactory.MESSAGES;
import robocup.component.tactics.AbstractState.STATES;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.Ball.UPDATE_MODE;
import robocup.component.worldobjects.Player.RELATIVE;
import robocup.component.worldobjects.Player.ROLE;

/**
 * A class for creating AttentionToActions based on the current world-model. It
 * should be extended, such that a state can select an attention value with a
 * certain logic.
 */
public class AttentionToActionFactory {

  /**
   * gets the attention to action for a certain player based on his state and
   * the world.
   * 
   * @param p -
   *          player
   * @param state -
   *          players state
   * @param world -
   *          world
   * @param prophet -
   *          prophet
   * @return AttentionTo Action
   */
  public synchronized static AttentionToAction getAction(final Player p,
      final STATES state,
      final WorldModel world,
      final Prophet prophet) {

    // always listen to own goalie during kick off
    if (world.getPlayMode() == PLAY_MODE.GOAL_KICK_OWN) {
      return new AttentionToAction(world.getAllPlayers()[0], world
          .getTeamName());
    }
    
    // always listen to own goalie during kick off
    if (world.getPlayMode() == PLAY_MODE.KICK_IN_OWN) {
      Player player = prophet.getFastestTeammateToBall();
    	return new AttentionToAction(player, world
          .getTeamName());
    }

    // say only attackers when near goal
    if (state == STATES.WATCH_GOALIE_OTHER) {
      return AttentionToActionFactory.getAttackerToListenTo(p, world);
    }

    final Player p_ = AttentionToActionFactory.getPlayerToListenTo(p, state, world,
        prophet);

    // some play modes
    if (p_ == null && world.getPlayMode() != PLAY_MODE.PLAY_ON) {
      return new AttentionToAction(world.getAllPlayers()[6], world
          .getTeamName());
    }

    // should not happen
    if (p_ == null) {
      System.out
          .println(world.getCycleAndPlayerString() + " no attention to action " + state);
      return new AttentionToAction(world.getAllPlayers()[6], world
          .getTeamName());
    }

    final AttentionToAction att = new AttentionToAction(p_, world.getTeamName());
    return att;

  }

  /**
   * Gets the attention to action for a certain player based on his state and
   * the world.
   * 
   * @param world -
   *          world
   * 
   * @return AttentionTo Action
   */
  private static AttentionToAction getAttackerToListenTo(final Player p,
      final WorldModel world) {

    int bestAge = Integer.MAX_VALUE;
    int age;
    Player bestPlayer = null;

    for (int i = 9; i < 12; i++) {
      final Player mate = world.getExpectedPlayerByRole(i);
      if (!mate.equals(p)) {
        age = mate
            .getAge(
                world.getBodyCycle(),
                robocup.component.worldobjects.Player.UPDATE_MODE.META_BASED_ON_INFO);
        if (age < bestAge) {
          bestAge = age;
          bestPlayer = mate;
        }
      }
    }

    return new AttentionToAction(bestPlayer, world.getTeamName());
  }

  /**
   * finds out who's is the most interesting player to listen to.
   * 
   * @param p -
   *          player
   * @param state -
   *          state
   * @param world -
   *          world
   * @param prophet -
   *          prophet
   * @return player to listen to
   */
  public static Player getPlayerToListenTo(final Player p,
      final STATES state,
      final WorldModel world,
      final Prophet prophet) {

    if(!world.getBallRef().isReliable()){
      return AttentionToActionFactory.tryToHearBall(p, world, prophet);
    }

    // if someone kicked he communicates the ball once again so listen to him
    if (world.getSelfRef().getLastHearedMessageType() == MESSAGES.KICKED_BALL && world
        .getSelfRef().getLastHearBodyCycle() == world.getBodyCycle()) {
      return world.getTeammates()[world.getSelfRef().getLastHearedPlayer() - 1];
    }

    // ball old -> listen to someone who sjould see (and communicate) it
    if (world.getBallRef().getAge(world.getBodyCycle(),
        UPDATE_MODE.META_BASED_ON_INFO) > DAInamiteConstants.WM_V_AGE_CONFIDENCE - 3) {
      return AttentionToActionFactory.tryToHearBall(p, world, prophet);
    }

    // player controls the ball -> listen to interesting teammates
    if (state.isBallControlState() || prophet.iAmFastestToBall()) {
      return AttentionToActionFactory.hearTeammateWithoutBallControl(p, world,
          prophet);
    }

    switch (prophet.inPossessionOfBall()) {
      case WE:
        // listen to player with ball, except he is still intercepting
        if (prophet.getInterceptionTime() < 4 || prophet.getInterceptionTime() % 2 == 0) {
          final Player p_ = AttentionToActionFactory.tryToHearTeammateWithBall(p,
              world, prophet);
          return p_;
        }
        else {
          // listen to teammates without ball
          return AttentionToActionFactory.hearTeammateWithoutBallControl(p,
              world, prophet);
        }

      case UNCERTAIN:
        // always listen to the involved player
        return AttentionToActionFactory.tryToHearTeammateWithBall(p, world,
            prophet);

      case OPPONENT:
        return AttentionToActionFactory
            .hearTeammateWithoutBallWhenInDefenseMode(p, world, prophet, state);
    }

    return null;
  }

  /**
   * used to listen to teammates when the opponent has the ball
   * 
   * @param p -
   *          player
   * @param world -
   *          world
   * @param prophet -
   *          prophet
   * @return best player to listen to
   */
  public synchronized static Player hearTeammateWithoutBallWhenInDefenseMode(final Player p,
      final WorldModel world,
      final Prophet prophet,
      final STATES state) {

    final LinkedList<WeightedPlayer> teammates = new LinkedList<WeightedPlayer>();

    for (int i = 0; i < 11; i++) {
      final Player mate = world.getExpectedPlayers()[i];

      // don't listen to the self or the last heared player
      if (mate.isMe()) {
      }
      else {
        if (state == STATES.MARK_OPPONENT_FROM_BALL || state == STATES.BACKFOUR_POSITIONING_NEAR_MIDLINE) {
          // if (mate.isGoalie() &&
          // world.getBallRef().getAge(world.getBodyCycle(),
          // UPDATE_MODE.META_BASED_ON_INFO) >
          // (DAInamiteConstants.WM_V_AGE_CONFIDENCE - 3)) {
          // teammates.add(new WeightedPlayer(mate));
          // break;
          // } else {
          final ROLE myRole = world.getSelfRef().getRoleByRoleNumber();
          switch (myRole) {
            case LEFT_BACK_FOUR:
              if (mate.getNumber() == 6 || mate.getNumber() == 7 || mate
                  .getNumber() == 8 || mate.isGoalie() || 
                  ROLE.META_POSITION_DEFENSE.contains(mate.getDefaultRole())) {
                teammates.add(new WeightedPlayer(mate));
              }
              break;
            case INLE_BACK_FOUR:
              if (mate.getNumber() == 6 || mate.getNumber() == 7 || mate
                  .getNumber() == 8 || mate.isGoalie() || 
                  ROLE.META_POSITION_DEFENSE.contains(mate.getDefaultRole())) {
                teammates.add(new WeightedPlayer(mate));
              }
              break;
            case INRE_BACK_FOUR:
              if (mate.getNumber() == 6 || mate.getNumber() == 7 || mate
                  .getNumber() == 8 || mate.isGoalie() || 
                  ROLE.META_POSITION_DEFENSE.contains(mate.getDefaultRole())) {
                teammates.add(new WeightedPlayer(mate));
              }
              break;
            case RIGHT_BACK_FOUR:
              if (mate.getNumber() == 6 || mate.getNumber() == 7 || mate
                  .getNumber() == 8 || mate.isGoalie() || 
                  ROLE.META_POSITION_DEFENSE.contains(mate.getDefaultRole())) {
                teammates.add(new WeightedPlayer(mate));
              }
              break;
            default:
              teammates.add(new WeightedPlayer(mate));
          }
          // }
        }
        else {
          teammates.add(new WeightedPlayer(mate));
        }
      }
    }

    double weight = 0;

    // set weights for each player
    for (final WeightedPlayer mate : teammates) {
      // age weight / distance weight
      weight = mate.player.getAge(world.getBodyCycle(),
          robocup.component.worldobjects.Player.UPDATE_MODE.META_BASED_ON_INFO) / p
          .getDistance(mate.player);

      // not seeable weight
      weight *= (Math.abs(p.getAngleRel(mate.player, RELATIVE.TO_BODY)) > 100) ? 2 : 1;

      // increase that ball-saying player, which is closer to me
      // (esimatedly)
      if ((world.getBallRef().getAge(world.getBodyCycle(),
          UPDATE_MODE.META_BASED_ON_INFO) > 2)) {
        if (mate.player.isGoalie() && world.getSelfRef().getPosition().y < -20) {
          weight *= 5;
        }
        else {
          weight *= 0.5;
        }
        if (mate.player.getNumber() == 7 && world.getSelfRef().getPosition().y >= -20) {
          weight *= 5;
        }
        else {
          weight *= 0.5;
        }
      }
      else {
        weight *= 0.5;
      }

      // // neighbour weight
      // weight *= world.isNeighbour(mate.player, p) ? 2 : 1;
      //      
      // // reliable weight
      // weight *= !mate.player.isReliable() ? 3 : 1;

      // // distance weight ball
      // weight /= mate.player.getDistance(world.getBallRef());

      // weight *= (mate.player.getRoleNumber() == 7 && p.getNumber() < 6) ? 10
      // : 1;

      weight *= (world.getBodyCycle() % mate.player.getNumber() == 0) ? 3 : 1;

      mate.weight = weight;

      if (state == STATES.MARK_OPPONENT_FROM_BALL || state == STATES.BACKFOUR_POSITIONING_NEAR_MIDLINE) {
        final Player self = world.getSelfRef();
        final int num = self.getRoleNumber();
        switch (num) {
          case 2:
            if (mate.player.getNumber() == 6) {
              weight *= 10;
            }
            else if (mate.player.getNumber() == 7) {
              weight *= 10;
            }
            break;
          case 3:
            if (mate.player.getNumber() == 6) {
              weight *= 10;
            }
            else if (mate.player.getNumber() == 7) {
              weight *= 10;
            }
            break;
          case 4:
            if (mate.player.getNumber() == 7) {
              weight *= 10;
            }
            else if (mate.player.getNumber() == 8) {
              weight *= 10;
            }
            break;
          case 5:
            if (mate.player.getNumber() == 7) {
              weight *= 10;
            }
            else if (mate.player.getNumber() == 8) {
              weight *= 10;
            }
            break;
        }

      }

      // in case the distance was 0
      if (Double.isNaN(weight)) {
        mate.weight = 0;
      }
    }

    Collections.sort(teammates);

    // return the player with the highest weight
    return teammates.getLast().player;
  }

  /**
   * used to listen to teammates without ball when we have the ball (use when we
   * have the ball or when we are not interested in the player with ball)
   * 
   * @param p -
   *          player
   * @param world -
   *          world
   * @param prophet -
   *          prophet
   * @return best player to listen to
   */
  public synchronized static Player hearTeammateWithoutBallControl(final Player p,
      final WorldModel world,
      final Prophet prophet) {

    final Player playerWithBall = prophet.getFastestTeammateToBall();

    final LinkedList<WeightedPlayer> teammates = new LinkedList<WeightedPlayer>();

    for (int i = 0; i < 11; i++) {
      final Player mate = world.getExpectedPlayers()[i];

      // don't listen to self, player with ball, last heared and goalie
      if (mate.isMe() || mate.equals(playerWithBall) || mate.getNumber() == p
          .getLastHearedPlayer() || mate.isGoalie()) {
      }
      else {
        teammates.add(new WeightedPlayer(mate));
      }
    }

    double weight = 0;

    for (final WeightedPlayer mate : teammates) {

      // age weight
      weight = mate.player.getAge(world.getBodyCycle(),
          robocup.component.worldobjects.Player.UPDATE_MODE.META_BASED_ON_INFO) / Math
          .pow(p.getDistance(mate.player), 2);

      // not seeable weight
      weight *= (Math.abs(p.getAngleRel(mate.player, RELATIVE.TO_BODY)) > 100) ? 2 : 1;

      // neighbour weight
      weight *= world.isNeighbour(mate.player, p) ? 2 : 1;

      // reliable weight
      weight *= !mate.player.isReliable() ? 3 : 1;
      mate.weight = weight;

      // in case a distance was 0
      if (Double.isNaN(weight)) {
        mate.weight = 0;
      }
    }

    Collections.sort(teammates);

    // return the player with the highest weight
    return teammates.getLast().player;

  }

  /**
   * used when our ball info get's old.
   * 
   * @param p -
   *          player
   * @param world -
   *          world
   * @param prophet -
   *          prophet
   * @return best player to listen to
   */
  public synchronized static Player tryToHearBall(final Player p,
      final WorldModel world,
      final Prophet prophet) {

    // when they loose the ball all players will start listening to the goalie
    // this doesn't make much sense for the goalie himself. Therefore he listens
    // to the center player
    if (p.isGoalie()) {
      if (p.getLastHearedPlayer() == world.getPlayerNumberByRole(7) - 1) {
        final Player p_ = world.getClosestTeammateToBall();
        if (p_.isMe()) {
          return world.getClosestTeammateToMe();
        }
        else {
          return world.getClosestTeammateToBall();
        }
      }
      else {
        return world.getAllPlayers()[world.getPlayerNumberByRole(7) - 1];
      }
    }

    // int he own field the goalie otherwise the center player is prefered
    if (world.getBallRef().getPosition().y < 0) {
      // if the goalie was heared last cycle and didn't say the ball (meaning he
      // lost the ball)
      // we listenen to the nearest player to the ball
      if (p.getLastHearedPlayer() == 1) {
        final Player p_ = world.getClosestTeammateToBall();
        if (p_.isMe()) {
          return world.getClosestTeammateToMe();
        }
        else {
          return world.getClosestTeammateToBall();
        }
      }
      else {
        return world.getAllPlayers()[0];
      }
    }
    else {
      if (p.getLastHearedPlayer() == world.getPlayerNumberByRole(7)) {
        final Player p_ = world.getClosestTeammateToBall();
        if (p_.isMe()) {
          return world.getClosestTeammateToMe();
        }
        else {
          return world.getClosestTeammateToBall();
        }
      }
      else {
        return world.getAllPlayers()[world.getPlayerNumberByRole(7) - 1];
      }
    }
  }

  /**
   * listens to the fastest-to-ball mate.
   * 
   * @param p -
   *          player
   * @param world -
   *          world
   * @param prophet -
   *          prophet
   * @return best player to listen to
   */
  public synchronized static Player tryToHearTeammateWithBall(final Player p,
      final WorldModel world,
      final Prophet prophet) {

    return prophet.getFastestTeammateToBall();
  }

}